#include "raknetserver.h"

RakNetServer::RakNetServer()
{
	m_threads_to_shutdown = 0;
	m_shutdown = false;
}

RakNetServer::~RakNetServer()
{
	// TODO: Is this necassary or even allowed?
	delete m_peer;
}

void RakNetServer::DoReceive()
{
	std::thread receive_thread([this] ()
	{
		RakNet::Packet *current_packet;

		// Always listen for packets until we are to shutdown.
		while (!m_shutdown)
		{
			// Prevent 100% CPU usage.
			std::this_thread::sleep_for(std::chrono::microseconds(1));

			for (current_packet = m_peer->Receive(); current_packet;
				m_peer->DeallocatePacket(current_packet), current_packet = m_peer->Receive())
			{
				Job new_job;
				new_job.data = new RakNet::BitStream(current_packet->data, current_packet->length, true);
				new_job.receive_guid = m_peer->GetMyGUID().ToString();
				new_job.sender_guid = current_packet->guid.ToString();
				m_work_manager.JobToLocal(new_job);
			}
		}

		// Thread ended.
		m_shutdown_mutex.lock();
		m_threads_to_shutdown--;
		m_shutdown_mutex.unlock();
	});

	m_shutdown_mutex.lock();
	m_threads_to_shutdown++;
	m_shutdown_mutex.unlock();

	// Leave receive thread running (asynchronous receive).
	receive_thread.detach();
}

void RakNetServer::DoSend()
{
	std::thread send_thread([this] ()
	{
		Job current_job;

		// Always check if there are jobs to be done.
		while (!m_shutdown)
		{
			// Prevent 100% CPU usage.
			std::this_thread::sleep_for(std::chrono::microseconds(1));

			while (m_work_manager.GetJobToRemote(current_job))
			{
				// If the broadcast flag was set, we don't need a receiver since we want to broadcast to everyone.
				// If it was not set, use the guid defined in the job.
				RakNet::RakNetGUID receiver;
				if (current_job.broadcast)
					receiver = RakNet::UNASSIGNED_RAKNET_GUID;
				else
					receiver.FromString(current_job.receive_guid.c_str());

				m_peer->Send(current_job.data, current_job.priority, current_job.reliability, 0, receiver, current_job.broadcast);

				current_job.JobDone();
			}
		}

		// Thread ended.
		m_shutdown_mutex.lock();
		m_threads_to_shutdown--;
		m_shutdown_mutex.unlock();
	});

	m_shutdown_mutex.lock();
	m_threads_to_shutdown++;
	m_shutdown_mutex.unlock();

	send_thread.detach();
}

void RakNetServer::Start(unsigned short port)
{
	RakNet::SocketDescriptor socket_descriptor(port, 0);

	// Startup as peer.
	m_peer = RakNet::RakPeerInterface::GetInstance();
	m_peer->Startup(MAX_PLAYERS - 1, &socket_descriptor, 1);
	m_peer->SetMaximumIncomingConnections(MAX_PLAYERS - 1);

	DoReceive();
	DoSend();
}

void RakNetServer::Shutdown()
{
	m_shutdown = true;

	// Wait until our threads have shutdown.
	while (true)
	{
		m_shutdown_mutex.lock();
		if (m_threads_to_shutdown <= 0)
		{
			m_shutdown_mutex.unlock();
			break;
		}
		m_shutdown_mutex.unlock();

		std::this_thread::sleep_for(std::chrono::nanoseconds(1));
	}

	RakNet::RakPeerInterface::DestroyInstance(m_peer);
}

void RakNetServer::CloseConnection(std::string guid)
{
	RakNet::RakNetGUID connection_guid;
	connection_guid.FromString(guid.c_str());

	m_peer->CloseConnection(connection_guid, true);
}